package org.flexthinker.binding.ui.form.binding.flex
{
    import flash.display.DisplayObject;

    import mx.utils.StringUtil;

    import org.flexthinker.binding.ui.form.binding.BindableUIForm;

    import org.flexthinker.binding.ui.form.binding.BindableUIForm;

    import org.flexthinker.binding.ui.form.binding.BindingFieldFinder;
    import org.flexthinker.binding.ui.form.binding.BindingFieldFinderListener;

    import spark.events.SkinPartEvent;

    /**
     * Default implementation of <code>BindingFieldFinder</code>.
     * <p>
     *     For performance reasons, it asks that the form is a <code>BindableUIForm</code>
     *     from which is reads the bindable fields using a method: <code>getBindableElements</code>
     *
     *     Besides, it listens for event of type <code>SkinPartEvent.PART_ADDED</code>
     *     in order to try to find new fields that could possible be used to auto-bind the form
     *     to its <code>FormBinding</code>
     * </p>
     */
    public class DefaultBindingFieldFinder implements BindingFieldFinder
    {
        private var listeners:Vector.<BindingFieldFinderListener> = new Vector.<BindingFieldFinderListener>();

        private var form:BindableUIForm;

        private var fields:Vector.<DisplayObject>;

        public function DefaultBindingFieldFinder(form:BindableUIForm):void
        {
            setForm(form);
        }

        public function getBindableFields():Vector.<DisplayObject>
        {
            if ( fields )
            {
                return fields;
            }

            fields = new Vector.<DisplayObject>();

            var elements:Vector.<DisplayObject> =  form.getBindableElements();
            for each ( var el:DisplayObject in elements )
            {
                if( el )
                {
                    fields.push(el);
                }
            }

            watchForNewFields();

            return fields;
        }

        public function reset():void
        {
            removeAllListeners();
            unwatch();
            fields = null;
        }

        public function setForm(value:Object):void
        {
            throwIfInvalidForm(value);
            this.form = BindableUIForm(value);
        }

        public function addFinderListener(listener:BindingFieldFinderListener):void
        {
            if (listener && listeners.indexOf(listener) < 0 )
            {
                listeners.push( listener );
            }
        }

        public function removeFinderListener(listener:BindingFieldFinderListener):void
        {
            var idx:int = listeners.indexOf(listener);
            if ( idx >= 0 )
            {
                listeners.splice(idx, 1);
            }
        }

        public function removeAllListeners():void
        {
            listeners = new Vector.<BindingFieldFinderListener>();
        }

        // -------------------
        //  internal methods
        // -------------------

        protected function throwIfInvalidForm(form:Object):void
        {
            if ( this.form )
            {
                throw new Error("Form is already set");
            }

            if ( form == null )
            {
                throw new ArgumentError("Unexpected form. Please provide a non-null form");
            }
        }

        private function watchForNewFields():void
        {
            form.addEventListener(SkinPartEvent.PART_ADDED, form_partAddedHandler, false, 0, true);
            form.addEventListener(SkinPartEvent.PART_REMOVED, form_partRemovedHandler, false, 0, true);
        }

        private function unwatch():void
        {
            form.removeEventListener(SkinPartEvent.PART_ADDED, form_partAddedHandler );
            form.removeEventListener(SkinPartEvent.PART_REMOVED, form_partRemovedHandler );
        }

        private function form_partAddedHandler(event:SkinPartEvent):void
        {
            if ( fields.indexOf(event.instance) < 0 )
            {
                fields.push( event.instance );
            }

            var n:int = listeners.length;
            for ( var i:int = 0 ; i< n; i++ )
            {
                listeners[i].fieldAdded(event.partName, event.instance);
            }
        }

        private function form_partRemovedHandler(event:SkinPartEvent):void
        {
            var idx:int = fields.indexOf(event.instance)
            if ( idx >= 0 )
            {
                fields.splice(idx, 1);
            }

            var n:int = listeners.length;
            for ( var i:int = 0 ; i< n; i++ )
            {
                listeners[i].fieldRemoved(event.partName, event.instance);
            }

        }
    }
}
